""" 
  程序逻辑过程：
    使用熔断机制，当亏的金额达到指定值，则停止下注，要另选一个模型下注。
    模型选取：要准确率达到54以上才符合要求。
 """

from distutils.log import fatal
import keras
from keras import regularizers
from keras.models import Sequential
from setting import Setting
import data
import os
import numpy as np
import matplotlib.pyplot as plt
from keras import backend
from keras import layers
from keras import models
from keras.models import load_model
import tool
import json
import time

 
def model(set, num="a0000", data_={}):
  """ 建立网络模型和训练
  :param set: 配置
  :param num: 保存模型文件的前缀
  :param data_: 要训练的数据集
   """
  inputs = []
  outputs = []
  x_input = layers.Input((set.MAX_STEPS, set.FRONT_VOCAB_SIZE))
  x = layers.Bidirectional(layers.LSTM(set.LSTM_UNITS, 
    return_sequences=True,
    use_bias=True,bias_initializer=keras.initializers.Zeros(),
    recurrent_dropout=set.DROPOUT_RATE,
    kernel_regularizer=regularizers.l2(set.REGULARIZERS),
    activity_regularizer=regularizers.l2(set.REGULARIZERS)))(x_input)
  x = layers.Flatten()(x)
  x = layers.Dense(set.FRONT_VOCAB_SIZE, activation='relu')(x)
  inputs.append(x_input)
  outputs.append(x)
  model = models.Model(inputs, outputs)
  model.compile(optimizer=keras.optimizers.Adam(), metrics = ['accuracy'],
    loss=keras.losses.categorical_crossentropy,
    loss_weights=[1, 1, 1, 1, 1])
  
  # 获取数据
  # data_ = data.getDateFromExeQuery(set)
  train_x = data_["train_x"]
  train_y = data_["train_y"]
  test_x = data_["test_x"]
  test_y = data_["test_y"]
  # 获取文件夹地址
  path = os.path.split(os.path.realpath(__file__))[0]
  # 创建保存模型的文件夹
  if not os.path.exists('{}/{}'.format(path,set.CHECKPOINTS_PATH)):
    os.mkdir('{}/{}'.format(path,set.CHECKPOINTS_PATH))

  # 开始训练
  epoch = set.EPOCHS
  print('模型训练开始：{}_{}_{}_{}_{}_{}_{}'.format(num, epoch, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS))
  isSuccess = True
  for i in range(epoch):
    H = model.fit(train_x, train_y, validation_data=(test_x, test_y), batch_size=set.BATCH_SIZE, epochs=1)
    if np.isnan(H.history["loss"][0]):
      backend.clear_session()
      isSuccess = False
      return {
        "isSuccess":isSuccess,
      }
    else:
      print("===")
  # 保持模型
  model.save('{}/{}/{}_{}_{}_{}_{}_{}_{}.h5'.format(path,set.CHECKPOINTS_PATH, num, epoch, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS))
  # 除之前的模型.
  backend.clear_session()

  print('模型训练完成：{}_{}_{}_{}_{}_{}_{}'.format(num, epoch, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS))

  return {
    "fileName": "{}_{}_{}_{}_{}_{}_{}.h5".format(num, epoch, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS),
    "isSuccess":isSuccess,
  }


def predict(set, num, data_={}, predictTotal=5):
  """ 单个预测 """
 # 获取文件夹地址
  path = os.path.split(os.path.realpath(__file__))[0]
  # 加载整个模型
  model = load_model('{}/{}/{}_{}_{}_{}_{}_{}_{}.h5'.format(path,set.CHECKPOINTS_PATH,num,set.EPOCHS, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS))
  # 获取数据
  # data_ = data.getDateFromExeQuery(set, True)
  predict_x = data_["predict_x"]
  issueNo = data_["issueNo"]
  # 开始预测
  predicts = model.predict(predict_x, batch_size=1)
  # 除之前的模型.
  backend.clear_session()
  for i in range(len(predicts)):
    balls= tool.sample(predicts[i], predictTotal=predictTotal)
  return {
    "predictValue":balls, # 下期要下注的预测值
    "issueNo":issueNo[-1:][0], # 当前期的期号
  }

def trainPredict_(set, num, data_={}, predictTotal=5):
  """ 多个预测并统计结果 """
  # 获取文件夹地址
  path = os.path.split(os.path.realpath(__file__))[0]
  # 加载整个模型
  model = load_model('{}/{}/{}_{}_{}_{}_{}_{}_{}.h5'.format(path,set.CHECKPOINTS_PATH, num, set.EPOCHS, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS))
  # 获取数据
  predict_x = data_["predict_x"]
  predict_y = data_["predict_y"]
  # 开始预测
  predicts = model.predict(predict_x, batch_size=1)
  # 除之前的模型.
  backend.clear_session()
  res = tool.Statistics(predicts, predict_y,fileName="{}_{}_{}_{}_{}_{}_{}".format(num, set.EPOCHS, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS),predictTotal=predictTotal)
  return res

def getOrSetData(set,data=None):
  """ 将真实预测的数据保存到本地文件。真实预测未来数据
    :param data: 要保存的数据。是一个对象。
   """
  path = os.path.split(os.path.realpath(__file__))[0]
  # 创建保存数据的文件
  try:
    f =open('{}/{}'.format(path,set.SavaDataFile),'r')
    f.close()
  except IOError:
    f = open('{}/{}'.format(path,set.SavaDataFile),'w')
  with open(path + "/" + set.SavaDataFile,'r') as load_f:
    try:
      load_dict = json.load(load_f)
    except Exception as e:
      load_dict = {} # 表示空白文件，则创建一个对象
  if data:
    load_dict = data
  else:
    return load_dict
  with open(path + "/" + set.SavaDataFile,"w") as f: # 将数据保存到文件中
    json.dump(load_dict,f,indent=2,ensure_ascii=True,cls=tool.NpEncoder)
    print("保存数据到文件完成...")
    
def zdylz(data_, total=1):
  """ 计算最大遗漏值 """
  zd = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  for i in range(len(data_)):
    for jjj in range(len(zd)):
      if data_[i] != jjj:
        zd[jjj] = zd[jjj] + 1
      else:
        zd[jjj] = 0
  tt = []
  for jjj in range(total):
    t =js(zd)
    tt.append(t)
    zd[tt[jjj]] = 0
  return tt
def js(data_):
  tt = 0
  zz = 0
  for jjj in range(len(data_)):
      if data_[jjj] > zz:
        zz = data_[jjj]
        tt = jjj
  return tt
  

if __name__ == '__main__':
  trainNumber = 153 # 训练的个数
  AccuracyRateRun = 53.5 # 只有大于AccuracyRateRun，才表示可能盈利。
  predictNumber = -11 # 计算预测准确率的数据个数
  fusinNumber = 15 # 熔断值。
  predictTotal = 9 # 预测的个数。去掉遗漏值之前的个数
  predictTotalReal=  5 # 预测的个数。
  yulouzhiTotal = 1 # 最大遗漏值的个数
  dui = 4.6 # 每次对是要增加的money
  cuo = 5 # 每次不对是要减去的money
  isOpenDelSelf = True # 是否开启不在在预测值中出现当前开奖号码

  set = Setting()
  set.setDef(SavaDataFile_= "predictModel__Fusing.json")
  savaData = getOrSetData(set)
  issueNoNew = 0 # 当前期号。
  predictValue=[] # 当前期号的预测值
  if "model" not in savaData:
    savaData["model"] = {}
    one = False
  else:
    one = True
  while True:
    # 训练
    trainList = [
      [5, 8, 16, 0.3, 16, 0.1],  # 3000:5s
      [5, 32, 16, 0.3, 16, 0.1], # 3000:3
      # [5, 8, 16, 0.3, 64, 0.1], # 3000:4
      # [5, 32, 16, 0.3, 64, 0.1], # 3000:2
      # [5, 4, 8, 0.3, 64, 0.01],
      # [5, 4, 8, 0.3, 16, 0.01],
    ]
    update_ = True
    ddaaa_ = []
    for i in range(0, len(trainList)):
      item = trainList[i]
      set.setDef(
        EPOCHS_=item[0],
        BATCH_SIZE_=item[1],
        MAX_STEPS_=item[2],
        DROPOUT_RATE_=item[3],
        LSTM_UNITS_=item[4],
        REGULARIZERS_=item[5],
        CHECKPOINTS_PATH_= "predictModel__Fusing",
        LOTTO_DOWNLOAD_URL_=trainNumber)
      num = "a0000"
      if i < 10:
        num = num + "{}_".format(i)
      elif i < 100:
        num = num[:-1] + "{}_".format(i)
      try:
        print("开始训练")
        dd = data.getDate2(set,predictTest=predictNumber, update=update_, data_=ddaaa_)
        update_ = False
        ddaaa_ = dd["load_data"]
        if not one:
          res = model(set, num, dd)
        ddd = trainPredict_(set, num, dd, predictTotal=predictTotalReal)
        if one or res["isSuccess"]:
          fileName = '{}_{}_{}_{}_{}_{}_{}.h5'.format(num, set.EPOCHS, set.BATCH_SIZE, set.MAX_STEPS,set.DROPOUT_RATE,set.LSTM_UNITS,set.REGULARIZERS)
          savaData["model"][fileName] = ddd
          savaData["model"][fileName]["num"] = num
          savaData["model"][fileName]["EPOCHS_"] = item[0]
          savaData["model"][fileName]["BATCH_SIZE_"] = item[1]
          savaData["model"][fileName]["MAX_STEPS_"] = item[2]
          savaData["model"][fileName]["DROPOUT_RATE_"] = item[3]
          savaData["model"][fileName]["LSTM_UNITS_"] = item[4]
          savaData["model"][fileName]["REGULARIZERS_"] = item[5]
          getOrSetData(set, data=savaData)
      except Exception as e:
        print("=========================== 训练错误 ====================")
        print(e)
    one = False
    goodModelFlag = False
    savaData["AccuracyRate"] = AccuracyRateRun # 只有大于AccuracyRate，才表示可能盈利。
    for key in savaData["model"]:
      if savaData["model"][key]["AccuracyRate"] >= savaData["AccuracyRate"]:
        savaData["AccuracyRate"] = savaData["model"][key]["AccuracyRate"]
        savaData["goodModel"] = key # 保存最好的模型用于下面的计算
        goodModelFlag = True
    getOrSetData(set, data=savaData)
    if goodModelFlag:
      print("最优模型为：{}，准确率为：{}".format(savaData["goodModel"], savaData["AccuracyRate"]))
      fileName = savaData["goodModel"]
      set.setDef(
        EPOCHS_=savaData["model"][fileName]["EPOCHS_"],
        BATCH_SIZE_=savaData["model"][fileName]["BATCH_SIZE_"],
        MAX_STEPS_=savaData["model"][fileName]["MAX_STEPS_"],
        DROPOUT_RATE_=savaData["model"][fileName]["DROPOUT_RATE_"],
        LSTM_UNITS_=savaData["model"][fileName]["LSTM_UNITS_"],
        REGULARIZERS_=savaData["model"][fileName]["REGULARIZERS_"],
        LOTTO_DOWNLOAD_URL_=savaData["model"][fileName]["MAX_STEPS_"] + 20)
      if "money" not in savaData:
        savaData["money"] = 0
        savaData["moneyMin"] = 0
        savaData["moneyMax"] = 0
      money_ = savaData["money"]  # 
      while True:
        try:
          da = data.authenticPredictData(set)
          issueNo = da["issueNo"][-1:][0]
          if issueNoNew == issueNo:
            print("休眠中……")
            time.sleep(10)
            continue
          else:
            predictRes = predict(set,savaData["model"][fileName]["num"],data_=da, predictTotal=predictTotal)
            # 计算最大遗漏值
            zd = zdylz(da["load_data"], total=yulouzhiTotal)
            print("预测值为：{}，遗漏值为：{}".format(predictRes["predictValue"], zd))
            predictValue_ = []
            for jjj in range(len(predictRes["predictValue"])):
              if isOpenDelSelf:
                # 是否开启不在在预测值中出现当前开奖号码
                if predictRes["predictValue"][jjj] == da["issueNoFive"]:
                  continue
              if predictRes["predictValue"][jjj] not in zd:
                predictValue_.append(predictRes["predictValue"][jjj])
            # 去除遗漏值后的总个数，并截取指定数量
            predictRes["predictValue"] = predictValue_[:predictTotalReal]
            if predictRes["issueNo"] == (issueNoNew + 1):
              if da["issueNoFive"] in predictValue:
                print("对了……")
                savaData["money"] += dui
              else:
                savaData["money"] -= cuo
                print("没有对噢……")
            predictValue = predictRes["predictValue"]
            issueNoNew = issueNo
          if savaData["money"] - money_ >= 0: # 
            money_ = savaData["money"]
          if savaData["money"] > savaData["moneyMax"]:
            savaData["moneyMax"] = savaData["money"]
          if savaData["money"] < savaData["moneyMin"]:
            savaData["moneyMin"] = savaData["money"]
          print("当前题目为：{}，值为：{}，预测值为：{}".format(predictRes["issueNo"],da["issueNoFive"], predictValue))
          print("当前得分为：{}".format(savaData["money"]))
          print("切换标记为：{}".format(money_))
          getOrSetData(set, data=savaData)
          # if money_ - savaData["money"] >= fusinNumber: # 熔断
          #   savaData["AccuracyRate"] = 0
          #   savaData["goodModel"] = "" # 
          #   savaData["model"] = {}
          #   getOrSetData(set, data=savaData)
          #   break
          break
        except Exception as e:
          print("=========================== 预测错误 ====================")
          print(e)